import os
import re
import yaml
from jinja2 import Environment, BaseLoader, Undefined

from eval_lib.common import logger

log = logger.get_logger()

# from mailmerge import MailMerge

from .base import ReportBase

# key is case_name pattern, value is TEMPLATE file path and case_group abbreviations
REPORT_TEMPLATE_LIST = {
    "performance_analysis_nginx.*": (
        "./report/templates/agent_performance_analysis_report_nginx.md",
        "performance_analysis_nginx"
    ),
    "performance_analysis_istio.*": (
        "./report/templates/agent_performance_analysis_report_istio.md",
        "performance_analysis_istio"
    ),
    "performance_analysis_traefik.*": (
        "./report/templates/agent_performance_analysis_report_traefik.md",
        "performance_analysis_traefik"
    ),
}


def get_report_template(case_name):
    """
    根据用例名称获取报告模板内容。
    
    参数:
    case_name: str - 用例的名称，用于匹配模板。
    
    返回值:
    str - 匹配到的模板内容。如果没有匹配到，则返回None。
    """
    content = None
    # 遍历报告模板列表，尝试匹配用例名称
    for k, v in REPORT_TEMPLATE_LIST.items():
        # 使用正则匹配当前用例名称
        match = re.match(k, case_name)
        if bool(match):
            # 如果匹配成功，读取对应的文件内容
            file_path = v[0]
            with open(file_path, "r") as f:
                content = f.read()
            break  # 找到匹配项后即停止循环
    return content


def get_report_index(case_name):
    """
    根据用例名称获取报告索引
    
    参数:
    case_name: str - 用例的名称
    
    返回值:
    int - 报告模板的索引，如果没有匹配的模板则返回None
    """
    template_index = None  # 初始化模板索引为None

    # 遍历报告模板列表，尝试匹配用例名称
    for k, v in REPORT_TEMPLATE_LIST.items():
        match = re.match(k, case_name)  # 使用正则匹配用例名称
        if bool(match):  # 如果匹配成功
            template_index = v[1]  # 设置模板索引
            break  # 结束循环

    return template_index


class Dict2Obj:
    """
    将字典转换为对象的类。
    
    参数:
    - d (dict): 要转换为对象的字典。字典的键可以是数字或字符串。如果键是数字，
      并且该数字对应于`values`列表的索引位置，则将值添加到该位置；
      如果键是字符串，则将值设置为对象的属性。
    """

    def __init__(self, d):
        self.values = []
        for k, v in d.items():
            # 检查值是否为字典，如果是，则将其转换为Dict2Obj对象
            if isinstance(v, dict):
                v = Dict2Obj(v)
            # 处理以数字为键的情况，将值添加到正确的索引位置
            if k.isdigit():
                index = int(k)
                if index == len(self.values):
                    self.values.append(v)
                elif index > len(self.values):
                    # 如果索引超出当前`values`长度，先填充空项，再添加值
                    self.values += [""] * (index - len(self.values))
                    self.values.append(v)
                elif index < len(self.values):
                    # 如果索引在`values`范围内，直接替换该索引位置的值
                    self.values[index] = v
            else:
                # 处理以字符串为键的情况，设置对象的属性
                setattr(self, k, v)

    def __getitem__(self, key):
        """
        根据给定的键获取对应的值。

        当键小于存储的值的长度时，返回对应位置的值；否则返回空字符串。

        参数:
        - key: int，要获取值的索引位置。

        返回值:
        - 返回索引对应的值或空字符串。
        """
        if key < len(self.values):
            return self.values[int(key)]
        else:
            return ""

    def __getattr__(self, key):
        if key.isdigit() and key < len(self.values):
            return self.values[int(key)]
        else:
            return ""


class SilentUndefined(Undefined):

    def __str__(self):
        return ""

    def __getitem__(self, key):
        return self

    def __getattr__(self, key):
        return self


class ReportMarkdown(ReportBase):

    def __init__(self, data_path):
        self.yaml_list = []

        self.data_path = data_path
        self.report_path = f"{data_path}/markdown/"
        if os.path.exists(self.report_path):
            pass
        else:
            log.info(f"mkdir {self.report_path}")
            os.mkdir(self.report_path)


    def load_data(self):
        """
        加载数据方法。
        此方法遍历指定的数据路径(self.data_path)，查找并读取所有以“.yaml”结尾的文件。
        将每个文件的内容安全地加载为YAML格式，并将其添加到一个列表中，供后续使用。
        """
        for file in os.listdir(self.data_path):  # 遍历数据路径中的所有文件
            if file.endswith(".yaml"):  # 检查文件是否以“.yaml”结尾
                file_path = os.path.join(self.data_path, file)  # 拼接文件的完整路径
                with open(file_path, "r") as f:  # 打开文件，准备读取
                    yaml_data = yaml.safe_load(f)  # 安全加载YAML数据
                    self.yaml_list.append(yaml_data)  # 将加载的数据添加到列表中

    def merge(self):
        """
        合并多个yaml数据，并根据模板生成报告。
        该方法首先会将yaml_list中具有相同case_name的项合并，然后根据合并后的数据生成报告。
        报告将根据模板渲染，并保存到指定的路径下。
        """

        def write(data):
            """
            根据给定的数据生成报告并写入文件。

            参数:
            - data: 一个字典，包含生成报告所需的所有数据，必须包含case_name键。

            无返回值。
            """
            # 获取报告模板并检查是否存在
            report_template = get_report_template(data["case_name"])
            if report_template is None:
                return
            # 获取报告索引
            template_index = get_report_index(data["case_name"])
            # 配置并加载模板引擎
            md_template = Environment(
                loader=BaseLoader, undefined=SilentUndefined
            ).from_string(report_template)
            # 将数据转换为Dict2Obj类型，以便在模板中更方便地使用
            data = Dict2Obj(data)
            # 根据模板和数据渲染报告
            report = md_template.render(data=data)
            # 构造报告文件路径并写入报告内容
            report_path = f"{self.report_path}/agnet-perfromance-report-{template_index}.md"
            with open(report_path, "w") as f:
                f.write(report)

        # 初始化一个空字典用于存储合并后的数据
        data = {}
        # 遍历yaml_list，将具有相同case_name的项合并
        for yaml_data in self.yaml_list:
            template_index = get_report_index(yaml_data["case_name"])
            if template_index not in data:
                data[template_index] = yaml_data
            else:
                data[template_index].update(yaml_data)
        # 对合并后的数据逐个生成报告
        for index, v in data.items():
            write(v)

    def run(self):
        try:
            flag = 0
            for file in os.listdir(self.data_path):
                if file.endswith(".yaml"):
                    flag = 1
                    break
            if flag == 0:
                return
            self.load_data()
            self.merge()
        except Exception as e:
            log.error(e)
